'use strict';

const Controller = require('egg').Controller;
const _ = require('lodash');
const path = require('path');
const fs = require('fs');
const uuidv1 = require('uuid/v1');
const awaitWriteStream = require('await-stream-ready').write;
const sendToWormhole = require('stream-wormhole');

class TopicController extends Controller {
  /**
   * Topic page
   */
  async index() {
    function isUped(user, reply) {
      if (!reply.ups) {
        return false;
      }
      return reply.ups.indexOf(user._id) !== -1;
    }

    const {ctx, service} = this;
    const topic_id = ctx.params.tid;
    const currentUser = ctx.user;

    if (topic_id.length !== 24) {
      ctx.status = 404;
      ctx.message = '此话题不存在或已被删除。';
      return;
    }

    const [topic, author, replies] = await service.topicSvc.getFullTopic(topic_id);

    if (!topic) {
      ctx.status = 404;
      ctx.message = '此话题不存在或已被删除。';
      return;
    }

    const canRead = await this.assertReadPermission(topic, currentUser);
    if (!canRead) {
      ctx.status = 403;
      if (currentUser) {
        ctx.message = '您无权限阅读此文。';
      } else {
        ctx.message = `您无权限阅读此文，请先 <a href="/signin?redirect_url=/topic/${topic.id}">登录</a>`;
      }
      return;
    }

    // 增加 visit_count
    topic.visit_count += 1;
    // 写入 DB
    await service.topicSvc.incrementVisitCount(topic_id);

    topic.author = author;
    topic.replies = replies;
    // 点赞数排名第三的回答，它的点赞数就是阈值
    topic.reply_up_threshold = (() => {
      let allUpCount = replies.map(reply => {
        return (reply.ups && reply.ups.length) || 0;
      });
      allUpCount = _.sortBy(allUpCount, Number).reverse();

      let threshold = allUpCount[2] || 0;
      if (threshold < 3) {
        threshold = 3;
      }
      return threshold;
    })();

    const options = {limit: 5, sort: '-last_reply_at'};
    const query = {author_id: topic.author_id, _id: {$nin: [topic._id]}};
    const other_topics = await service.topicSvc.getTopicsByQuery(query, options);

    // get no_reply_topics
    let no_reply_topics = await service.cacheSvc.get('no_reply_topics');
    if (!no_reply_topics) {
      const query = {reply_count: 0, tab: {$nin: ['job', 'dev']}};
      const options = {limit: 5, sort: '-createdAt'};
      no_reply_topics = await service.topicSvc.getTopicsByQuery(query, options);
      await service.cacheSvc.setex('no_reply_topics', no_reply_topics, 60 * 1);
    }

    let is_collect;
    if (!currentUser) {
      is_collect = null;
    } else {
      is_collect = await service.topicCollectSvc.getTopicCollect(
        currentUser._id,
        topic_id
      );
    }

    let tags = [];
    if (topic.tags) {
      tags = topic.tags.map((v) => {
        return {name: v}
      });
    }

    let mention_users = null;
    if (currentUser) {
      mention_users = await service.atSvc.getMentionUsers(currentUser.nickname);
    }

    let is_attention = false;
    if (currentUser && currentUser.followings.map(v => v.name).includes(topic.author_name)) {
      is_attention = true;
    }

    await ctx.renderX('topic/index.nj', {
      topic,
      author_other_topics: other_topics,
      no_reply_topics,
      is_uped: isUped,
      is_collect,
      mention_users,
      tags,
      is_attention
    });
  }

  /**
   * 进入创建主题页面
   */
  async create() {
    const {ctx, config, service} = this;
    const currentUser = ctx.user;
    const mention_users = await service.atSvc.getMentionUsers(ctx.user.nickname);
    const my_original_tags = await service.topicSvc.listOriginalTags(ctx.user._id);
    await ctx.renderX('topic/edit.nj', {
      tabs: config.canEditTabs,
      is_public: true,
      my_original_tags: my_original_tags,
      mention_users,
      user: currentUser,
      tags: [],
      tagsString: '',
      tab: 'original',
      topic_id: undefined
    });
  }

  /**
   * 发表主题帖
   */
  async put() {
    const {ctx, service} = this;
    const {tabs} = this.config;
    const {body} = ctx.request;
    const user_id = ctx.user._id;

    // 得到所有的 tab, e.g. ['ask', 'share', ..]
    const allTabs = tabs.map(tPair => tPair[0]);

    // 使用 egg_validate 验证
    // TODO: 此处可以优化，将所有使用egg_validate的rules集中管理，避免即时新建对象
    const RULE_CREATE = {
      title: {
        type: 'string',
        max: 100,
        min: 3,
      },
      content: {
        type: 'string',
      },
      tab: {
        type: 'enum',
        values: allTabs,
      },
    };
    ctx.validate(RULE_CREATE, ctx.request.body);

    // 储存新主题帖
    const topic = await service.topicSvc.newAndSave(
      body.title.trim(),
      body.content,
      body.tab,
      user_id,
      undefined,
      !body.is_private,
      body.topic_type,
      body.original_tags
    );

    // 发帖用户增加积分,增加发表主题数量
    let topic_count = 1;
    // 如果在分享版块, 或者是私密文章, 则用户的文章记录数不加1
    if (body.tab == 'share' || body.is_private) {
      topic_count = 0;
    }
    await service.userSvc.incrementScoreAndTopicCount(topic.author_id, 5, topic_count);

    // 通知被@的用户
    await service.atSvc.sendMessageToMentionUsers(
      body.content,
      topic._id,
      ctx.user._id
    );

    // 把@过的人缓存起来
    await this.service.atSvc.cacheMentionUsers(ctx.user.nickname, body.content);

    // 删除标签缓存
    let cache_key = `original_tags:${ctx.user.id}`;
    await this.service.cacheSvc.del(cache_key);


    await this.ctx.helper.notifySuperAdmin(user_id, topic.id);

    ctx.redirect('/topic/' + topic._id);
  }

  /**
   * 显示编辑页面
   */
  async showEdit() {
    const {ctx, service, config} = this;
    const topic_id = ctx.params.tid;
    const currentUser = ctx.user;
    const {topic} = await service.topicSvc.getTopicById(topic_id);
    const my_original_tags = await service.topicSvc.listOriginalTags(ctx.user._id);
    let tags = [];
    if (topic.tags) {
      tags = topic.tags.map((v) => {
        return {name: v}
      });
    }

    if (!topic) {
      ctx.status = 404;
      ctx.message = '此话题不存在或已被删除。';
      return;
    }

    if (
      String(topic.author_id) === String(ctx.user._id) ||
      ctx.user.is_admin
    ) {
      const mention_users = await service.atSvc.getMentionUsers(ctx.user.nickname);
      // console.log('tags', tags);
      await ctx.renderX('topic/edit.nj', {
        action: 'edit',
        topic_id: topic._id,
        title: topic.title,
        content: topic.content,
        tab: topic.tab,
        tabs: config.tabs,
        is_public: topic.is_public,
        topic_type: topic.topic_type,
        my_original_tags,
        user: currentUser,
        tags,
        tagsString: tags.map(item => item.name).join(' '),
        mention_users
      });
    } else {
      ctx.status = 403;
      ctx.message = '对不起，你不能编辑此话题';
    }
  }

  /**
   * 更新主题帖
   */
  async update() {
    const {ctx, service, config} = this;
    let current_time = new Date();
    const currentUser = ctx.user;
    const topic_id = ctx.params.tid;
    let {title, tab, content, is_private, original_tags, topic_type} = ctx.request.body;

    const {topic} = await service.topicSvc.getTopicById(topic_id);
    if (!topic) {
      ctx.status = 404;
      ctx.message = '此话题不存在或已被删除。';
      return;
    }

    if (
      topic.author_id.toString() === ctx.user._id.toString() || ctx.user.is_admin
    ) {
      title = title.trim();
      tab = tab.trim();
      content = content.trim();

      // 验证
      let editError;
      if (title === '') {
        editError = '标题不能是空的。';
      } else if (title.length < 3 || title.length > 100) {
        editError = '标题字数太多或太少。';
      } else if (!tab) {
        editError = '必须选择一个版块。';
      } else if (content === '') {
        editError = '内容不可为空。';
      }
      // END 验证

      if (editError) {
        await ctx.renderX('topic/edit.nj', {
          action: 'edit',
          edit_error: editError,
          topic_id: topic._id,
          user: currentUser,
          content,
          tabs: config.tabs,
        });
        return;
      }

      // 保存话题
      topic.title = title;
      topic.content = content;
      topic.tab = tab;
      topic.updatedAt = current_time;
      topic.is_public = !is_private;

      if (topic.original_url && topic.sub_tab != '瞎扯') {
        topic.original_url = content.replace(/[\s\S]+(http.+)/, '$1');
      }
      if (topic.tab == 'original') {
        topic.intro = content.replace(/[^_/:：,.;'，。、；a-zA-Z0-9\u4e00-\u9fa5]/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 300);
      }
      if (topic_type) {
        topic.topic_type = topic_type;
      }
      if (original_tags) {
        if (Array.isArray(original_tags)) {
          topic.tags = Array.from(new Set(original_tags));
        } else {
          topic.tags = Array.from(new Set(original_tags.trim().split(/\s+/)));
        }
        topic.tags = this.ctx.helper.getUniqueArrayCaseNotSensitive(topic.tags);
      }

      await topic.save();

      await ctx.model.TopicCollect.updateMany({topic_id: topic._id},
        {title: topic.title, original_url: topic.original_url, is_public: topic.is_public, updatedAt: current_time});


      await service.atSvc.sendMessageToMentionUsers(
        content,
        topic._id,
        ctx.user._id
      );

      // 把@过的人缓存起来
      await service.atSvc.cacheMentionUsers(ctx.user.nickname, content);

      // 删除标签缓存
      let cache_key = `original_tags:${ctx.user.id}`;
      await this.service.cacheSvc.del(cache_key);

      ctx.redirect('/topic/' + topic._id);
    } else {
      ctx.status = 403;
      ctx.message = '对不起，你不能编辑此话题。';
    }
  }

  /**
   * 删除主题帖
   */
  async delete() {
    // 删除话题, 话题作者topic_count减1
    // 删除回复，回复作者reply_count减1
    // 删除topic_collect，用户collect_topic_count减1
    // collect_tags表里, topic_ids去除topic_id
    const {ctx, service} = this;
    const topic_id = ctx.params.tid;

    const [topic, author] = await service.topicSvc.getFullTopic(topic_id);

    if (!topic) {
      ctx.status = 422;
      ctx.body = {message: '此话题不存在或已被删除。', success: false};
      return;
    }

    if (!ctx.user.is_admin && topic.author_id != ctx.user._id) {
      ctx.status = 403;
      ctx.body = {message: '无权限', success: false};
      return;
    }

    if (topic.tab == 'share') {
      author.score -= 3;
    } else {
      author.score -= 5;
    }

    // 不在分享版块, 并且是公开的文章, 用户的文章记录数才减1
    if (topic.tab != 'share' && topic.is_public) {
      author.topic_count -= 1;
    }

    await author.save();

    // 真实删除
    await topic.remove();


    // 删除文章的同时, 把收藏也要删了
    let collectUsers = await service.topicCollectSvc.getCollectUsersByTopicId(topic_id);
    for (let collectUser of collectUsers) {
      const user = await service.userSvc.getUserById(collectUser.user_id);
      user.collect_topic_count -= 1;
      await user.save();
    }
    await service.topicCollectSvc.removeAllUserCollect(topic_id);


    ctx.body = {message: '话题已被删除。', success: true};
  }

  /**
   * 设为置顶
   */
  async top() {
    const {ctx, service} = this;
    const topic_id = ctx.params.tid;
    const referer = ctx.helper.getReferer(topic_id);

    const topic = await service.topicSvc.getTopic(topic_id);

    if (!topic) {
      ctx.status = 404;
      ctx.message = '此话题不存在或已被删除。';
      return;
    }
    topic.top = !topic.top;
    await topic.save();
    const msg = topic.top ? '此话题已置顶。' : '此话题已取消置顶。';
    await ctx.renderX('notify/notify.nj', {success: msg, referer});
  }

  /**
   * 设为精华
   */
  async good() {
    const {ctx, service} = this;
    const topic_id = ctx.params.tid;
    const referer = ctx.helper.getReferer(topic_id);

    const topic = await service.topicSvc.getTopic(topic_id);
    if (!topic) {
      ctx.status = 404;
      ctx.message = '此话题不存在或已被删除。';
      return;
    }
    topic.good = !topic.good;
    await topic.save();
    const msg = topic.good ? '此话题已加精。' : '此话题已取消加精。';
    await ctx.renderX('notify/notify.nj', {success: msg, referer});
  }

  /**
   * 锁定帖子,不能回复
   */
  async lock() {
    const {ctx, service} = this;
    const topic_id = ctx.params.tid;
    const referer = ctx.helper.getReferer(topic_id);

    const topic = await service.topicSvc.getTopic(topic_id);
    if (!topic) {
      ctx.status = 404;
      ctx.message = '此话题不存在或已被删除。';
      return;
    }
    topic.lock = !topic.lock;
    await topic.save();
    const msg = topic.lock ? '此话题已锁定。' : '此话题已取消锁定。';
    await ctx.renderX('notify/notify.nj', {success: msg, referer});
  }

  /**
   * 上传
   */
  async upload() {
    const {ctx, config, service} = this;
    const uid = uuidv1();
    const stream = await ctx.getFileStream();
    const filename = uid + path.extname(stream.filename).toLowerCase();

    // 如果有七牛云的配置,优先上传七牛云
    if (config.qn_access && config.qn_access.secretKey !== 'your secret key') {
      try {
        const result = await service.topicSvc.qnUpload(stream, filename);
        ctx.body = {
          success: true,
          url: config.qn_access.origin + '/' + result.key,
        };
      } catch (err) {
        await sendToWormhole(stream);
        throw err;
      }
    } else if (config.cos) {
      try {
        const result = await service.topicSvc.cosUpload(stream, filename);
        ctx.body = {
          success: true,
          url: 'http://' + result,
        };
      } catch (err) {
        await sendToWormhole(stream);
        throw err;
      }
    } else {
      const target = path.join(config.upload.path, filename);
      const writeStream = fs.createWriteStream(target);
      try {
        await awaitWriteStream(stream.pipe(writeStream));
        ctx.body = {
          success: true,
          url: config.upload.url + filename,
        };
      } catch (err) {
        await sendToWormhole(stream);
        throw err;
      }
    }
  }

  /**
   * 上传(vditor)
   */
  async upload4Vditor() {
    const {ctx, config, service} = this;
    const uid = uuidv1();
    const stream = await ctx.getFileStream();
    const filename = uid + path.extname(stream.filename).toLowerCase();

    // 如果有七牛云的配置,优先上传七牛云
    if (config.qn_access && config.qn_access.secretKey !== 'your secret key') {
      try {
        const result = await service.topicSvc.qnUpload(stream, filename);
        ctx.body = {
          success: true,
          url: config.qn_access.origin + '/' + result.key,
        };
      } catch (err) {
        await sendToWormhole(stream);
        throw err;
      }
    } else if (config.cos) {
      try {
        const result = await service.topicSvc.cosUpload(stream, filename);

        let bodyResult = {
          "msg": "",
          "code": 0,
          "data": {
            "errFiles": [],
            succMap: {}
          }
        };
        const picUrl = 'https://' + result;
        const picName = stream.filename.replace(/[^(a-zA-Z0-9\u4e00-\u9fa5\.)]/g, '').replace(/[\?\\/:|<>\*\[\]\(\)\$%\{\}@~]/g, '').replace('/\\s/g', '');
        bodyResult.data.succMap[picName] = picUrl;
        ctx.body = bodyResult;
      } catch (err) {
        await sendToWormhole(stream);
        throw err;
      }
    } else {
      const target = path.join(config.upload.path, filename);
      const writeStream = fs.createWriteStream(target);
      try {
        await awaitWriteStream(stream.pipe(writeStream));
        ctx.body = {
          success: true,
          url: config.upload.url + filename,
        };
      } catch (err) {
        await sendToWormhole(stream);
        throw err;
      }
    }
  }

  async incrementVisitCount() {
    const {ctx, service} = this;
    const topic_id = ctx.params.tid;
    await service.topicSvc.incrementVisitCount4API(topic_id);
    ctx.body = {success: true};
  }

  async assertReadPermission(topic, user) {
    if (topic.is_public === true) {
      return true;
    }

    if (user && user.is_super_admin) {
      return true;
    }

    // 如果是私密文章, 未登录用户不可见
    if (!user) {
      return false;
    }

    if (topic.author_name == user.loginname) {
      return true;
    }
    if (topic.content.includes(`@${user.loginname}`)) {
      return true;
    }

    let first_reply = await this.service.replySvc.getFirstReplyByTopicId(topic.id);

    if (!first_reply) {
      return false;
    }

    // 防止有人伪造作者授权, 第一条必须是作者的评论
    if (first_reply.author_id != topic.author_id) {
      return false;
    }

    let first_reply_content = first_reply.content;
    if (first_reply_content.includes('@all') || first_reply_content.includes('@所有人')) {
      return true;
    }
    if (first_reply_content.includes(`@${user.loginname}`)) {
      return true;
    }
    return false;
  }


  async assertTopicExist() {
    const {ctx, service} = this;
    const title = ctx.request.body.title.trim();
    const topic_id = ctx.request.body.topic_id;
    const author_id = ctx.user._id;
    const tab = 'original';
    let query = {title, tab, author_id};
    if (topic_id) {
      query._id = {$ne: topic_id};
    }
    let result = await ctx.model.Topic.findOne(query);
    ctx.body = {
      success: true,
      data: result != null,
    };
  }
}

module.exports = TopicController;
